/*
 * @Description: In User Settings Edit
 * @Author: your name
 * @Date: 2018-12-31 17:28:29
 * @LastEditTime: 2018-12-31 18:36:57
 * @LastEditors: Please set LastEditors
 */
// 变量的作用域
// 如果该变量前面没有带上关键字
// var， 这个变量就会成为全局变量
// 用
// var 关键字在函数中声明变量，
// 这时候的变量即是局部变量， 只有在该函数内部才能访问到这个变量， 在函数外面是访问不到的

// 变量的生存周期
// 对于全局变量来说，全局变量的生存周期当然是永久的，除非我们主动销毁这个全局变量。
// 而对于在函数内用 var 关键字声明的局部变量来说，
// 当退出函数时，它们都会随着函数调用的结束而被销毁
var func = function() {
    var a = 1; // 退出函数后局部变量 a 将被销毁 
    alert(a);
};
// func();

// 闭包可以延续变量的生存周期
var func = function() {
    var a = 1;
    return function() {
        a++;
        alert(a);
    }
};
var f = func();
// f(); // 输出:2
// f(); // 输出:3
// f(); // 输出:4
// f(); // 输出:5

// 闭包的更多作用
// 封装变量
// 延续局部变量的寿命
var mult = (function() {
    var cache = {};
    var calculate = function() { // 封闭 calculate 函数
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
            a = a * arguments[i];
        };
        return a;
    }

    return function() {
        var args = Array.prototype.join.call(arguments, ',');
        if (args in cache) {
            return cache[args];
        }
        return cache[args] = calculate.apply(null, arguments);
    }
})();

// alert(mult(1, 2, 3)); // 输出:6
// alert(mult(1, 2, 3)); // 输出:6

// 闭包实现命令模式
// 闭包与内存管理
// 可能会引起内存泄漏
// 如果两个对象之间形成了循环引用，那么这两个对象都无法被回收，
// 但循环引用造成的内存泄露在本质上也不是闭包造成的

// 高阶函数
// 高阶函数是指至少满足下列条件之一的函数
// 函数可以作为参数被传递
// 函数可以作为返回值输出

// 函数作为参数传递
// 回调函数
var getUserInfo = function(userId, callback) {
    $.ajax('http://xxx.com/getUserInfo?' + userId, function(data) {
        if (typeof callback === 'function') {
            callback(data);
        }
    });
}

// getUserInfo(13157, function(data) {
//     alert(data.userName);
// });

// 委托
var appendDiv = function(callback) {
    for (var i = 0; i < 100; i++) {
        var div = document.createElement('div');
        div.innerHTML = i;
        document.body.appendChild(div);
        if (typeof callback === 'function') {
            callback(div);
        }
    };
};
// appendDiv(function(node) {
//     node.style.display = 'none';
// });

// 函数作为返回值输出
// 判断数据的类型

var Type = {};
for (var i = 0, type; type = ['String', 'Array', 'Number'][i++];) {
    (function(type) {
        Type['is' + type] = function(obj) {
            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
        }
    })(type)
};
Type.isArray([]); // 输出:true
Type.isString("str"); // 输出:true


// getSingle
var getSingle = function(fn) {
    var ret;
    return function() {
        return ret || (ret = fn.apply(this, arguments));
    };
};
var getScript = getSingle(function() {
    return document.createElement('script');
});
var script1 = getScript();
var script2 = getScript();
// alert(script1 === script2); // 输出:true


// 高阶函数实现AOP （面向切面编程 ）
// AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来，
// 这些 跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
// 把这些功能抽离出来之后， 再通过“动态织入”的方式掺入业务逻辑模块中。
Function.prototype.before = function(beforefn) {
    var __self = this; // 保存原函数的引用
    return function() { // 返回包含了原函数和新函数的"代理"函数
        beforefn.apply(this, arguments);
        return __self.apply(this, arguments);
    }
};
Function.prototype.after = function(afterfn) {
    var __self = this;
    return function() {
        // 执行新函数，修正 this // 执行原函数
        var ret = __self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
};
var func = function() {
    console.log(2);
};
func = func.before(function() {
    console.log(1);
}).after(function() {
    console.log(3);
});
// func(); //  1  2  3

// 高阶函数的其他应用
// currying 又称部分求值。一个 currying 的函数首先会接受一些参数，
// 接受了这些参数之后， 该函数并不会立即求值，而是继续返回另外一个函数，
// 刚才传入的参数在函数形成的闭包中被保 存起来。待到函数被真正需要求值的时候，
// 之前传入的所有参数都会被一次性用于求值。

var currying = function(fn) {
    var args = [];
    return function() {
        if (arguments.length === 0) {
            return fn.apply(this, args);
        } else {
            [].push.apply(args, arguments);
            return arguments.callee;
        }
    }
};
var cost = (function() {
    var money = 0;
    return function() {
        for (var i = 0, l = arguments.length; i < l; i++) {
            money += arguments[i];
        }
        return money;
    }
})();
var cost = currying(cost); // 转化成 currying 函数
cost(100); // 未真正求值
cost(200); // 未真正求值
cost(300); // 未真正求值 
// alert(cost()); // 求值并输出:600

// uncurrying
Function.prototype.uncurrying = function() {
    var self = this; // self 此时是 Array.prototype.push
    return function() {
        var obj = Array.prototype.shift.call(arguments); // 相当于 Array.prototype.push.apply( obj, 2 ) };
    };
};
var push = Array.prototype.push.uncurrying();
var obj = {
    "length": 1,
    "0": 1
};
push(obj, 2);
// 这没有明白  浏览器执行错误
console.log(obj); // 输出:{0: 1, 1: 2, length: 2}

// 函数节流：将即将被执行的函数用 setTimeout 延迟一段时间执行。
// 如果该次延迟执行还没有完成，则忽略接下来调用该函数的请求
var throttle = function(fn, interval) {
    var __self = fn, // 保存需要被延迟执行的函数引用 timer, // 定时器
        firstTime = true; // 是否是第一次调用
    return function() {
        var args = arguments,
            __me = this;
        if (firstTime) { // 如果是第一次调用，不需延迟执行 
            __self.apply(__me, args);
            return firstTime = false;
        }
        if (timer) { // 如果定时器还在，说明前一次延迟执行还没有完成 
            return false;
        }
        timer = setTimeout(function() { // 延迟一段时间执行           
            clearTimeout(timer);
            timer = null;
            __self.apply(__me, args);
        }, interval || 500);
    };
};
window.onresize = throttle(function() {
    console.log(1);
}, 500);